# coding=utf-8
import json
import uuid
from typing import Dict

from obespoir.rpcserver.push_lib import multicast_msg
from obespoir.server.frame.frame_server import BattleStatus
from obespoir.share.singleton import Singleton
from obespoir.server.frame.frame_manager import FrameManager
from obespoir.share.espoir_log import global_log as logger

from package.game_const import ReadyStatus, RoomType, GameStatus
from package.msg_const import MsgID
from server.service.room.base_room import BaseRoom
from server.service.user_manager import UserManager


class GameManager(object, metaclass=Singleton):

    def __init__(self):
        # game_id: BaseRoom
        self._rooms: Dict[str, BaseRoom] = {}

        # user_id: game_id
        self._user_game_map: Dict[str, str] = {}

    def create_room(self, user_id: str, game_type: int, room_type: int) -> str:
        game_id = uuid.uuid4().hex
        room_info = BaseRoom(game_id, game_type, room_type)
        room_info.members[user_id] = ReadyStatus.NOT_READY
        self._rooms[game_id] = room_info
        self._user_game_map[user_id] = game_id
        return game_id

    def join_room(self, game_id: str, user_id: str) -> None:
        if game_id not in self._rooms:
            return
        self._rooms[game_id].members[user_id] = ReadyStatus.NOT_READY
        self._user_game_map[user_id] = game_id

    def join_match_room(self, user_id: str, game_type: int) -> str:
        for game_id, room_info in self._rooms.items():
            if room_info.game_type == game_type and \
                    room_info.room_type == RoomType.MATCH and \
                    not room_info.is_full():
                self.join_room(game_id, user_id)
                return game_id
        game_id = self.create_room(user_id, game_type, RoomType.MATCH)
        return game_id

    def check_start_game(self, game_id: str) -> bool:
        if game_id not in self._rooms:
            logger.warning("game_id not in rooms", game_id=game_id)
            return False
        game_info = self._rooms[game_id]
        if game_info.is_full() and game_info.is_all_ready():
            return True
        else:
            return False

    async def start_game(self, game_id: str) -> None:
        if game_id not in self._rooms:
            return

        self._rooms[game_id].game_status = GameStatus.RUNNING
        users = UserManager().get_frame_users(
            list(self._rooms[game_id].members.keys()))
        self._rooms[game_id].frame_server = FrameManager().create_server(
            game_id, users)

        self._rooms[game_id].frame_server.status = BattleStatus.STARTING

        # 通知玩家游戏开始
        session_ids = [user.session_id for user in users]
        await multicast_msg(
            session_ids, MsgID.GAME_START,
            json.dumps({
                "team_users": [
                    {"user_id": user.user_id,
                     "user_name": user.extra.get("user_name",
                                                 "")}
                    for user in users],
                "random_seed": self._rooms[game_id].random_seed,
            }))

    def check_game_over(self, game_id: str) -> bool:
        if game_id not in self._rooms:
            logger.warn("game_id not in rooms")
            return False
        game_info = self._rooms[game_id]
        if game_info.is_full() and game_info.is_all_ready():
            return True
        else:
            return False

    async def game_over(self, game_id: str, is_win: bool) -> None:
        if game_id not in self._rooms:
            return
        self._rooms[game_id].game_status = GameStatus.GAME_OVER
        # 通知玩家游戏结束
        users = UserManager().get_frame_users(
            list(self._rooms[game_id].members.keys()))
        session_ids = [user.session_id for user in users]
        await multicast_msg(session_ids, MsgID.GAME_OVER,
                            json.dumps({"game_id": game_id, "is_win": is_win}))

        FrameManager().delete_server(game_id)
        del self._rooms[game_id]

    def set_ready_status(self, user_id: str, status: int) -> str:
        game_id = self._user_game_map.get(user_id, "")
        if game_id and game_id not in self._rooms:
            return ""
        game_info = self._rooms[game_id]
        game_info.members[user_id] = status
        return game_id

    def add_op(self, user_id: str, op: Dict) -> None:
        game_id = self._user_game_map.get(user_id, "")
        if game_id and game_id not in self._rooms:
            return
        game_info = self._rooms[game_id]
        op["user_id"] = user_id
        game_info.frame_server.add_op(op)
